﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ValiVIN
{
    /// <summary>
    /// Class to assist with VIN decoding.
    /// 
    /// Example Usage:
    /// VINumber myVIN = "11111111111111111";
    /// txtResult.Text = myVIN.ToString();
    /// </summary>
    public class VINumber
    {
        #region fields
        string _vin;
        bool _isDecoded;
        #endregion

        public VINumber() { this.AutoDecode = DEFAULT_AutoDecode; }
        public VINumber(string vin)
        {
            this.AutoDecode = DEFAULT_AutoDecode;
            this.VIN = vin;
        }
        public VINumber(bool autoDecode)
        {
            this.AutoDecode = autoDecode;
        }
        public VINumber(string vin, bool autoDecode)
        {
            this.AutoDecode = autoDecode;
            this.VIN = vin;
        }

        /// <summary>
        /// If the object should Automatically Decode the VIN when the this.VIN is set
        /// </summary>
        public bool AutoDecode { get; set; }

        /// <summary>
        /// If the current VIN has been decoded
        /// </summary>
        public bool IsDecoded { get { return _isDecoded; } }

        /// <summary>
        /// Bool Informing if the VIN passes CheckSum validation
        /// </summary>
        public bool Valid
        {
            get;
            internal set;
        }

        /// <summary>
        /// Setting this to true will attempt to decode a vin even if it fails CheckSum Validation
        /// when you set the Vin Property.
        /// </summary>
        public bool DecodeIfInvalid
        {
            get;
            set;
        }

        /// <summary>
        /// Country of origin
        /// </summary>
        public string Country
        {
            get;
            internal set;
        }

        /// <summary>
        /// Manufacturer of the Vehicle
        /// </summary>
        public string Manufacturer
        {
            get;
            internal set;
        }

        /// <summary>
        /// Region of the Given VIN
        /// 
        /// (North America, South America, Europe, Oceanic, Asia, Africa)
        /// </summary>
        public string Region
        {
            get;
            internal set;
        }

        /// <summary>
        /// Earliest decoded possible year for the Vehicle
        /// </summary>
        public int Year1
        {
            get;
            internal set;
        }

        /// <summary>
        /// Second decoded possible year for the Vehicle
        /// </summary>
        public int Year2
        {
            get;
            internal set;
        }

        /// <summary>
        /// VIN
        /// Setting this automatically Decodes and sets the other properties
        /// in this ValiVIN object
        /// </summary>
        public string VIN
        {
            get { return _vin; }
            set 
            {
                if (_vin != value)
                {
                    _vin = value.ToUpper() ?? string.Empty;
                    _isDecoded = false;
                    if(this.AutoDecode)
                        Decode();
                }
            }
        }

        /// <summary>
        /// Gives the most likely year from the VIN.
        /// This Property Assumes the most recent year is more likely.
        /// </summary>
        public int Year
        {
            get
            {
                return Year2 > Year1 ? Year2 : Year1;
            }
        }

        #region Manufacturer Specific

        /// <summary>
        /// Manufacturing Plant
        /// </summary>
        public string Plant
        {
            get;
            internal set;
        }

        /// <summary>
        /// Engine Type (Manufacturer Specific)
        /// </summary>
        public string EngineType
        {
            get;
            internal set;
        }

        /// <summary>
        /// Serial Number (Manufacturer Specific)
        /// </summary>
        public string SerialNumber
        {
            get;
            internal set;
        }

        public Dictionary<string, string> ManufacturerSpecifics
        {
            get;
            internal set;
        }

        #endregion

        /// <summary>
        /// Decodes the VIN given to this class.
        /// Sets the: Manufacturer, Valid, Country, Year (Year1, Year2), and Region properties.
        /// 
        /// This is automatically called when you set the VIN
        /// </summary>
        public void Decode()
        {
            this.Valid = VINCheck.IsValid(this.VIN);
            this.Manufacturer = this.Valid || this.DecodeIfInvalid ? VINCheck.Manufacturer(this.VIN) : string.Empty;
            this.Country = this.Valid || this.DecodeIfInvalid ? VINCheck.CountryOfOrigin(this.VIN) : string.Empty;
            this.Year1 = this.Valid || this.DecodeIfInvalid ? VINCheck.Year1(this.VIN) : 0;
            this.Year2 = this.Valid || this.DecodeIfInvalid ? VINCheck.Year2(this.VIN) : 0;
            this.Region = this.Valid || this.DecodeIfInvalid ? VINCheck.Region(this.VIN) : string.Empty;

            //Manufacturer Specific
            if (this.Valid || this.DecodeIfInvalid)
                ManufacturerDecode.Decode(this);
            else
                ManufacturerDecode.ClearManufacturerSpecifics(this);

            _isDecoded = true;
        }

        /// <summary>
        /// Decodes the VIN given to this class.
        /// Sets the: Manufacturer, Valid, Country, Year (Year1, Year2), and Region properties.
        /// </summary>
        /// <param name="decodeIfInvalid">if true, continues decoding even if vin is invalid</param>
        public void Decode(bool decodeIfInvalid)
        {
            this.Valid = VINCheck.IsValid(this.VIN);
            this.Manufacturer = this.Valid || decodeIfInvalid ? VINCheck.Manufacturer(this.VIN) : string.Empty;
            this.Country = this.Valid || decodeIfInvalid ? VINCheck.CountryOfOrigin(this.VIN) : string.Empty;
            this.Year1 = this.Valid || decodeIfInvalid ? VINCheck.Year1(this.VIN) : 0;
            this.Year2 = this.Valid || decodeIfInvalid ? VINCheck.Year2(this.VIN) : 0;
            this.Region = this.Valid || decodeIfInvalid ? VINCheck.Region(this.VIN) : string.Empty;

            //Manufacturer Specific
            if (this.Valid || decodeIfInvalid)
                ManufacturerDecode.Decode(this);
            else
                ManufacturerDecode.ClearManufacturerSpecifics(this);
            
            _isDecoded = true;
        }


        /// <summary>
        /// Gives a multi line string of the VIN decode
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append(this.VIN ?? string.Empty);
            sb.AppendLine();
            sb.Append("Valid: ");
            sb.Append(this.Valid ? "Yes" : "No");

            if (this.Valid)
            {
                if (!string.IsNullOrEmpty(this.Region))
                {
                    sb.AppendLine();
                    sb.Append("Region: ");
                    sb.Append(this.Region);
                }
                if (!string.IsNullOrEmpty(this.Country))
                {
                    sb.AppendLine();
                    sb.Append("Country: ");
                    sb.Append(this.Country);
                }
                if (!string.IsNullOrEmpty(this.Manufacturer))
                {
                    sb.AppendLine();
                    sb.Append("Manufacturer: ");
                    sb.Append(this.Manufacturer);
                }

                if (this.Year > 0)
                {
                    sb.AppendLine();
                    if (this.Year != this.Year2) //only year1 is set
                    {
                        sb.Append("Year: ");
                        sb.Append(this.Year.ToString());
                    }
                    else //assume both years are set
                    {
                        sb.Append("Year: ");
                        sb.Append(this.Year1.ToString());
                        sb.Append(" Or ");
                        sb.Append(this.Year2.ToString());
                    }
                }

                if (!string.IsNullOrEmpty(this.Plant))
                {
                    sb.AppendLine();
                    sb.Append("Plant: ");
                    sb.Append(this.Plant);
                }

                if (!string.IsNullOrEmpty(this.EngineType))
                {
                    sb.AppendLine();
                    sb.Append("Engine Type: ");
                    sb.Append(this.EngineType);
                }

                foreach(string s in this.ManufacturerSpecifics.Keys)
                {
                    sb.AppendLine();
                    sb.Append(s + ": ");
                    sb.Append(this.ManufacturerSpecifics[s]);
                }

                if (!string.IsNullOrEmpty(this.SerialNumber))
                {
                    sb.AppendLine();
                    sb.Append("Serial Number: ");
                    sb.Append(this.SerialNumber);
                }
            }

            return sb.ToString();
        }


        #region Comparison

        /// <summary>
        /// Determines if an Object represents the Same VIN as this object.
        /// Can compare to a string.
        /// Ignores Decoded values.
        /// </summary>
        /// <param name="obj">Object to compare</param>
        /// <returns>true if the VIN is the same</returns>
        public override bool Equals(System.Object obj)
        {
            if (ReferenceEquals(this, obj)) { return true; }
            if (obj == null) { return false; }
            if (string.IsNullOrEmpty(this.VIN)) { return false; }
            if(!(obj is VINumber)) { return false; }

            VINumber nVin = obj as VINumber;
            if (nVin == null) { return false; }

            return this.VIN.Equals(nVin.VIN);            
        }

        public bool Equals(string str)
        {
            if (string.IsNullOrEmpty(this.VIN)) { return false; }
            return this.VIN.Equals(str);
        }

        public override int GetHashCode()
        {
            return this.VIN.GetHashCode();
        }

        #endregion

        #region Static

        public static implicit operator string(VINumber vin)
        {
            return vin.VIN;
        }
        

        public static implicit operator VINumber(string str)
        {
            return new VINumber(str);
        }

        private static bool _DEFAULT_AutoDecode = true;
        /// <summary>
        /// The default AutoDecode setting for future ValiVIN objects
        /// </summary>
        public static bool DEFAULT_AutoDecode
        {
            get
            {
                return _DEFAULT_AutoDecode;
            }
            set
            {
                _DEFAULT_AutoDecode = value;
            }
        }

        #endregion

    }
}
